#!/usr/bin/env python
#
# layout.py
#
#  Copyright (c) 2006-2010  Yusuke Shinyama <yusuke at cs dot nyu dot edu>
#  
#  Permission is hereby granted, free of charge, to any person
#  obtaining a copy of this software and associated documentation
#  files (the "Software"), to deal in the Software without
#  restriction, including without limitation the rights to use,
#  copy, modify, merge, publish, distribute, sublicense, and/or
#  sell copies of the Software, and to permit persons to whom the
#  Software is furnished to do so, subject to the following
#  conditions:
#  
#  The above copyright notice and this permission notice shall be
#  included in all copies or substantial portions of the Software.
#  
#  THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY
#  KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
#  WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR
#  PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
#  COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
#  LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
#  OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
#  SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
#

import sys, re
from htmldom import HTMLText
from style import Style
from utils import chunk_text, safeint, elist
from utils import divu, lowerbound, upperbound
INF = sys.maxint

# todo: link
# todo: navigation
# todo: jpchar curses

# todo: table width specified
# todo: form, script
# todo: pre
# todo: cookie
# todo: frame
# todo: multicol
# todo: auto transpose


#####################################################################

##  Placer
##
class Placer(object):

  """A cursor-like object that indicates the next position.
  
     (block)
     placer.start_style(e.style)
     placer.start_block()
     ...
     placer.end_block(container)
     placer.end_style(e.style)
     
     (inline)
     placer.start_style(e.style)
     ...
     placer.end_style(e.style)
  """

  debug = 0
  
  def __init__(self, container, stylesheet):
    self.container = container          # target to place
    self.stylesheet = stylesheet        # stylesheet to use
    self.default_style = stylesheet.device.default_style
    self._style_stack = []
    self._style = self.default_style    # current style
    self._block_stack = []
    self._cx = 0                        # position to place
    self._cy = 0
    self._plines = []                   # physical lines
    self._pline_objs = []
    self._last_char = 0                 # char type flag
    self._last_margin = 0
    return

  def __repr__(self):
    return '<Placer: (%d,%d)>' % (self._cx, self._cy)

  def get_current_style(self):
    return self._style

  def get_base_em(self):
    return self.get_current_style()['font'].size

  def _update_style(self, style1):
    (style0, self._style) = (self._style, style1)
    # XXX border support?
    self.xmargin = (style0['margin_left'] + style0['padding_left'] + style0['text_indent'])
    if style0['position'] == 'relative':
      self.xmargin += style0['left']
      self._cx += style0['left']
    width = style0['width'] or self.container.width
    self.xlimit = width - self.xmargin - (style0['padding_right'] + style0['margin_right'])
    return

  def get_style(self, style1):
    return self._style.override(self.container, style1, self.default_style)

  def start_style(self, style):
    self._style_stack.append(self._style)
    self._update_style(self.get_style(style))
    return

  def end_style(self):
    self._update_style(self._style_stack.pop())
    return

  def new_line(self, force=False):
    "Make sure the next object will start from the beginning of a line."
    # Have anything displayed within this line?
    if self._pline_objs:
      if self._plines:
        # If this is not the first line of the block, insert a leading space.
        self._cy += self._style['line_space']
      # Adjust the objects based on their height, ascent and descent.
      #   -+--+----------- -------------
      #    |  | +--+         ^        ^
      #    |  | |  | +--+  (ascent)   |
      #    |  | |  | |  |    v        |
      #   -+--+-+--+-+--+- -----   (height)
      #         +--+ |  |    ^        |
      #              |  | (descent)   |
      #              |  |    v        v
      #              +--+- -------------
      self._cy += max( obj.height-descent for (_,descent,obj) in self._pline_objs )
      if self.debug:
        print 'new_line: y=%d, %r' % (self._cy, self._pline_objs)
      self._plines.append((self._cx, self._cy, self._pline_objs))
      # The line height is the max of the descent of all the objects.
      self._cy += max( descent for (_,descent,obj) in self._pline_objs )
    elif force:
      # <br>
      self._cy += self._style['blank_line_height']
    # Reset the position.
    self._cx = 0
    self._pline_objs = []
    # 0:bol, 1:blank, 2:obj, 3:obj-blank
    self._last_char = 0
    return

  def _finish_last_block(self, vmargin, (xmin, ymin, xmax, ymax)=(INF, INF, -INF, -INF)):
    "Make sure the current line is finised."
    self.new_line()
    # Have anything displayed in this block?
    style = self._style
    if not self._plines:
      self._last_margin = max(self._last_margin, vmargin)
    else:
      # Align the objects in the block.
      xmargin = self.xmargin
      xlimit = self.xlimit
      align = style['object_align']
      if align == 'center':
        adjust = (lambda w: lowerbound(0, xlimit-w)/2)
      elif align == 'right':
        adjust = (lambda w: lowerbound(0, xlimit-w))
      else:
        adjust = (lambda w: 0)
      dy = self._last_margin
      if style['position'] == 'relative':
        dy += style['top']
      for (width, y0, objs) in self._plines:
        dx = adjust(width) + xmargin
        y0 += dy
        for (x,descent,obj) in objs:
          x += dx
          y = y0 - (obj.height-descent)
          obj.moveto(x, y)
          xmin = min(xmin, x)
          ymin = min(ymin, y)
          xmax = max(xmax, x+obj.width)
          ymax = max(ymax, y+obj.height)
      self._plines = []
      # ----c-e-i-l-i-n-g----+  <-- finish_last_block(prev.margin_top)
      #                      | prev.margin_top
      #  +----------------+  +
      #  | previous block |
      #  +----------------+  +  <-- finish_last_block(prev.margin_bottom)
      #                      |  <-- finish_last_block(cur.margin_top)
      #                      | max(prev.para_space, prev.margin_bottom, cur.margin_top)
      #  +----------------+  +
      #  | current  block |
      #  +----------------+  +  <-- finish_last_block(cur.margin_bottom)
      #                      |  <-- finish_last_block(next.margin_top)
      #                      | max(cur.para_space, cur.margin_bottom, next.margin_top)
      #  +----------------+  +
      #  |   next block   |
      #  +----------------+  +  <-- finish_last_block()
      #                      | next.margin_bottom
      # -----b-o-t-t-o-m-----+
      self._cy += dy
      self.container.expand(xmax, self._cy)
      self._last_margin = vmargin
    return (xmin, ymin, xmax, ymax)

  def start_block(self, style, block):
    style = self.get_style(style)
    self._finish_last_block(style['margin_top'])
    self._style_stack.append(self._style)
    self._block_stack.append(block)
    self._update_style(style)
    return

  def end_block(self):
    style = self._style
    bound = self._finish_last_block(max(style['para_space'], style['margin_bottom']))
    for block in self._block_stack:
      block.set_bound(bound)
    self._block_stack.pop()
    self._update_style(self._style_stack.pop())
    return

  def put(self, descent, obj):
    if self._pline_objs and self.xlimit < self._cx+obj.width:
      self.new_line()
    self._pline_objs.append((self._cx, descent, obj))
    if self.debug:
      print 'put:', (self._cx, descent, obj)
    self._cx += obj.width
    self._last_char = 2
    if self.xlimit <= self._cx:
      self.new_line()
    return

  def layout_text(self, text):
    (s, twidth, theight) = ('', 0, 0)
    font = self._style['font']
    for c in chunk_text(text):
      if not c:
        self._last_char |= 1
        continue
      (w,h) = font.char_size(c)
      if self._last_char == 3:
        s += ' '
        twidth += font.blank_width
      if 2 <= self._last_char and self.xlimit <= self._cx+twidth+w:
        obj = TextComponent(self.container.device, font, s, twidth, theight)
        self.put(font.descent, obj)
        self.new_line()
        yield obj
        (s, twidth, theight) = (c, w, max(theight,h))
      else:
        (s, twidth, theight) = (s+c, twidth+w, max(theight,h))
      self._last_char = 2
    if s:
      last_char = self._last_char
      obj = TextComponent(self.container.device, font, s, twidth, theight)
      self.put(font.descent, obj)
      yield obj
      self._last_char = last_char
    return


#####################################################################


##  Component
##
class Component(object):

  debug = 0

  def __init__(self, device, parent, element, placer):
    self.device = device
    self.parent = parent
    self.element = element
    self.placer = placer
    return
  
  def __repr__(self):
    return '<%s>' % self.__class__.__name__

  # Determine my position.
  def build(self):
    return

  # Render itself and all the decendants.
  def render(self, x_offset, y_offset, window=None, focus=None, context=None):
    return
  

##  TangibleMixin
##
class TangibleMixin(object):

  def __init__(self):
    (self.x, self.y) = (0, 0)
    (self.width, self.height) = (0, 0)
    return

  def expand(self, width, height):
    self.width = max(self.width, width)
    self.height = max(self.height, height)
    return

  def overlap(self, (x0, y0, x1, y1)):
    return ((not (self.x+self.width < x0 or x1 < self.x)) and
            (not (self.y+self.height < y0 or y1 < self.y)))

  def moveto(self, x, y):
    (self.x, self.y) = (x, y)
    return

  def get_width(self):
    return self.width
  def get_height(self):
    return self.height


##  Focusable Objects
##
class FocuserMixin(object):
  
  def __init__(self):
    self.focusable_items = []
    return
  
  def register_item(self, obj):
    self.focusable_items.append(obj)
    return

  def get_items(self):
    return self.focusable_items
  

##  TextComponent
##
class TextComponent(TangibleMixin, Component):

  def __init__(self, device, font, text, width, height):
    Component.__init__(self, device, None, None, None)
    TangibleMixin.__init__(self)
    self.font = font
    self.text = text
    self.expand(width, height)
    return
  
  def __repr__(self):
    return '<Text: (%s,%s) %r>' % (self.x, self.y, self.text)

  def render(self, x, y, window=None, focus=None, context=None):
    self.device.draw_text(self.font, x+self.x, y+self.y, self.text)
    return

  def build(self):
    raise ValueError


#####################################################################

  
##  Container
##
class Container(Component):

  COMPONENTS = {}
  
  def __init__(self, device, parent, element, placer):
    Component.__init__(self, device, parent, element, placer)
    self.children = []
    (self.x0, self.y0, self.x1, self.y1) = (INF, INF, -INF, -INF)
    return

  def __repr__(self):
    return ('<%s: (%d,%d)-(%d,%d) %r>' %
            (self.__class__.__name__,
             self.x0, self.y0, self.x1, self.y1,
             self.children))

  def set_bound(self, bound):
    if not bound: return
    (x0, y0, x1, y1) = bound
    self.x0 = min(self.x0, x0)
    self.y0 = min(self.y0, y0)
    self.x1 = max(self.x1, x1)
    self.y1 = max(self.y1, y1)
    return

  def overlap(self, (x0, y0, x1, y1)):
    return ((not (self.x1 < x0 or x1 < self.x0)) and
            (not (self.y1 < y0 or y1 < self.y0)))

  def build(self):
    if not self.element.children: return
    for e in self.element.children:
      if isinstance(e, HTMLText):
        self.children.extend(self.placer.layout_text(e.text))
      else:
        try:
          child = self.COMPONENTS[e.tag](self.device, self, e, self.placer)
        except KeyError:
          if self.debug:
            print 'notfound: %r' % e.tag
          continue
        child.build()
        self.children.append(child)
    return

  def set_focusable(self):
    self.placer.container.register_item(self)
    return
  
  def get_base_em(self):
    return self.placer.get_base_em()
  
  def render(self, x, y, window=None, focus=None, context=None):
    for obj in self.children:
      if not window or obj.overlap(window):
        obj.render(x, y, window=window, focus=focus, context=context)
    return

  
##  BlockContainer
##
class BlockContainer(Container):
  
  def build(self):
    style = self.element.style          # could be None.
    if self.element.tag == 'center':
      style = Style(device=self.device, parent=style, object_align='center')
    elif self.element['align']:
      style = Style(device=self.device, parent=style, object_align=self.element['align'])
    elif style:
      style = Style(device=self.device, parent=style, object_align=style['text_align'])
    self.placer.start_block(style, self)
    Container.build(self)
    self.placer.end_block()
    return

  
##  TangibleComponent
##
class TangibleComponent(TangibleMixin, Component):
  
  def __init__(self, device, parent, element, placer):
    Component.__init__(self, device, parent, element, placer)
    TangibleMixin.__init__(self)
    return
  
  def build(self):
    self.placer.put(0, self)
    return


##  ImageComponent
##
class ImageComponent(TangibleComponent):

  def build(self):
    style = self.placer.get_style(self.element.style)
    self.expand(int(safeint(self.element['width'],1) * style['xscale']),
                int(safeint(self.element['height'],1) * style['yscale']))
    align = self.element['align'] or style['vertical_align']
    if align == 'middle':
      descent = self.height/2
    elif align == 'top':
      descent = self.height - self.placer.get_base_em()
    else:
      descent = 0
    self.placer.put(descent, self)
    return
  
  def render(self, x, y, window=None, focus=None, context=None):
    self.device.draw_image(x+self.x, y+self.y,
                           self.width, self.height,
                           self.element['src'],
                           self.element['alt'])
    return
  
  
#####################################################################

##  TransparentContainer
##
class TransparentContainer(Container):

  def build(self):
    self.placer.start_style(self.element.style)
    Container.build(self)
    self.placer.end_style()
    return

  def overlap(self, window):
    return True


##  LinkContainer
##
class LinkContainer(TransparentContainer):

  def build(self):
    TransparentContainer.build(self)
    self.set_focusable()
    return

  def render(self, x_offset, y_offset, window=None, focus=None, context=None):
    context = (context or {}).copy()
    context['link'] = True
    Container.render(self, x_offset, y_offset,
                     window=window, focus=focus, context=context)
    return
  

##  QuoteContainer
##
class QuoteContainer(TransparentContainer):

  def build(self):
    self.placer.start_style(self.element.style)
    self.children.extend(self.placer.layout_text(u'"'))
    Container.build(self)
    self.children.extend(self.placer.layout_text(u'"'))
    self.placer.end_style()
    return


##  LineBreakComponent
##
class LineBreakComponent(TangibleComponent):

  def build(self):
    self.placer.new_line(force=True)
    return


##  HorizontalBreakComponent
##
class HorizontalBreakComponent(TangibleComponent):
  
  def build(self):
    style = self.placer.get_style(self.element.style)
    self.expand(safeint(self.element['width'], style['width']),
                safeint(self.element['size'], style['height']))
    self.placer.put(0, self)
    return

  def render(self, x, y, window=None, focus=None, context=None):
    self.device.draw_hline(x+self.x, y+self.y, self.width)
    return


##  ListContainer
##
class ListContainer(BlockContainer):

  def __init__(self, device, parent, element, placer):
    BlockContainer.__init__(self, device, parent, element, placer)
    return


##  ListItemContainer
##
class ListItemContainer(BlockContainer):

  def render(self, x, y, window=None, focus=None, context=None):
    font = self.placer.get_current_style()['font']
    self.device.draw_text(font, x+self.x0-2, y+self.y0, '*')
    BlockContainer.render(self, x, y, window=window, focus=focus, context=context)
    return


##  TableContainer
##
class TableContainer(TangibleMixin, Container):

  # Row
  class Row(object):

    def __init__(self, parent):
      self.parent = parent
      self.children = []
      self.height = 0
      return

    def __repr__(self):
      return '<Row: height=%d>' % self.height

    def add(self, obj):
      self.children.append(obj)
      return

    def vexpand(self, height):
      self.height = max(self.height, height)
      return

    def vmoveto(self, y):
      self.y = y
      return

  # Column
  class Column(object):

    def __init__(self, parent):
      self.parent = parent
      self.children = []
      self.weight = 0
      self.width = 0
      self.default_width = 0
      return

    def __repr__(self):
      return '<Column: width=%d>' % self.width

    def set_default_width(self, width):
      self.default_width = width
      return

    def add(self, obj):
      self.children.append(obj)
      self.weight += sum( len(s) for s in obj.element.get_text() )
      return

    def hexpand(self, width):
      self.width = max(self.width, width)
      return

    def hmoveto(self, x):
      self.x = x
      return

  # Cell
  class Cell(FocuserMixin, Container):

    def __init__(self, device, parent, rows, cols, border, element, stylesheet):
      Container.__init__(self, device, parent, element, Placer(self, stylesheet))
      FocuserMixin.__init__(self)
      self.rows = rows
      self.cols = cols
      self.border = border
      (self.width, self.height) = (0, 0)
      for p in rows:
        p.add(self)
      for p in cols:
        p.add(self)
      return

    def expand(self, width, height):
      return

    def build(self):
      style = self.element['_style']
      style = Style(device=self.device, parent=style, object_align=self.element['align'])
      # set the tentative boundary.
      self.width = sum( col.default_width+self.border for col in self.cols ) - self.border
      self.placer.start_block(style, self)
      Container.build(self)
      self.placer.end_block()
      # expand tentatively.
      width = self.x1 - self.x0
      height = self.y1 - self.y0
      if len(self.cols) == 1:
        self.cols[0].hexpand(width)
      else:
        width0 = sum( col.width+self.border for col in self.cols ) - self.border
        dw = divu(width-width0, len(self.cols))
        for col in self.cols:
          col.hexpand(col.width+dw)
      if len(self.rows) == 1:
        self.rows[0].vexpand(height)
      else:
        height0 = sum( row.height+self.border for row in self.rows ) - self.border
        dh = divu(height-height0, len(self.rows))
        for row in self.rows:
          row.vexpand(row.height+dh)
      return

    def adjust_size(self):
      self.width = sum( col.width+self.border for col in self.cols ) - self.border
      self.height = sum( row.height+self.border for row in self.rows ) - self.border
      return

    def adjust_pos(self):
      height0 = self.height
      valign = self.element['valign']
      if valign == 'middle':
        self.dy = (self.height - height0)/2
      elif valign == 'bottom':
        self.dy = self.height - height0
      else:
        self.dy = 0
      return
    
    def render(self, x, y, window=None, focus=None, context=None):
      x += self.cols[0].x - self.x0
      y += self.rows[0].y - self.y0
      if self.border:
        self.device.draw_rect(x-1, y-1, self.width+2, self.height+2)
      Container.render(self, x, y, window=window, focus=focus, context=context)
      return
    
  ###

  def __init__(self, device, parent, element, placer):
    Container.__init__(self, device, parent, element, placer)
    TangibleMixin.__init__(self)
    return

  def __repr__(self):
    return '<TableContainer: (%s,%s) %sx%s, children=%r>' % \
           (self.x, self.y, self.width, self.height, self.children)

  def build(self):
    if not self.element.children: return
    style = self.element.style
    if self.element['border']:
      style = Style(device=self.device, parent=style,
                    table_border=safeint(self.element['border'], 1))
    if self.element['align']:
      style = Style(device=self.device, parent=style,
                    object_align=self.element['align'])
    self.placer.start_block(style, self)
    border = self.placer.get_current_style()['table_border']
    colsep = border or 1
    # count the number of rows/cols.
    cols = elist(default=lambda : TableContainer.Column(self))
    children = self.element.children[:]
    for x in self.element.children:
      if x.tag in ('thead', 'tbody'):
        children.extend(x.children)
    trs = [ tr for tr in children if tr.tag == 'tr' ]
    rows = [ TableContainer.Row(self) for tr in trs ]
    tab = elist(default=elist)
    y = 0
    for tr in trs:
      x = 0
      for td in tr.children:
        if td.tag not in ('td','th'): continue
        while tab[y][x]:
          x += 1
        (rowspan, colspan) = (upperbound(len(rows)-y, lowerbound(1, safeint(td['rowspan'], 1))),
                              lowerbound(1, safeint(td['colspan'], 1)))
        rowparents = [ rows[i] for i in xrange(y,y+rowspan) ]
        colparents = [ cols[i] for i in xrange(x,x+colspan) ]
        cell = TableContainer.Cell(self.device, self, rowparents, colparents,
                                   border, td, self.placer.stylesheet)
        self.children.append(cell)
        for y1 in xrange(y, y+rowspan):
          for x1 in xrange(x, x+colspan):
            tab[y1][x1] = 1
        x += colspan
      y += 1
    # estimate initial column widths.
    weight = max(1, sum( col.weight for col in cols ))
    width = self.placer.xlimit-border*2
    for col in cols:
      col.set_default_width(lowerbound(1, divu(width*col.weight, weight)))
    # place cells.
    for cell in self.children:
      cell.build()
    for cell in self.children:
      cell.adjust_size()
    # adjust the positions.
    x = border
    for (i,col) in enumerate(cols):
      if i and col.width:
        x += colsep
      col.hmoveto(x)
      x += col.width
    x += border
    y = border
    for (i,row) in enumerate(rows):
      if i and row.height:
        y += colsep
      row.vmoveto(y)
      y += row.height
    y += border
    self.expand(x, y)
    for cell in self.children:
      cell.adjust_pos()
    self.placer.put(0, self)
    self.placer.end_block()
    return
  
  def render(self, x, y, window=None, focus=None, context=None):
    Container.render(self, x+self.x, y+self.y, window=window, focus=focus, context=context)
    return


##  Frame
##
class Frame(TangibleMixin, FocuserMixin, TransparentContainer):

  def __init__(self, device, root, width, stylesheet):
    placer = Placer(self, stylesheet)
    Container.__init__(self, device, None, root, placer)
    TangibleMixin.__init__(self)
    FocuserMixin.__init__(self)
    self.width = width
    return
  

#####################################################################

Container.COMPONENTS.update({
  'body': TransparentContainer,
  'p': BlockContainer,
  'div': BlockContainer,
  'address': BlockContainer,
  'center': BlockContainer,
  'img': ImageComponent,
  'h1': BlockContainer,
  'h2': BlockContainer,
  'h3': BlockContainer,
  'h4': BlockContainer,
  'h5': BlockContainer,
  'h6': BlockContainer,
  'li': ListItemContainer,
  'ul': ListContainer,
  'ol': ListContainer,
  'dl': ListContainer,
  'a': LinkContainer,
  'b': TransparentContainer,
  'i': TransparentContainer,
  'u': TransparentContainer,
  's': TransparentContainer,
  'q': QuoteContainer,
  'strong': TransparentContainer,
  'em': TransparentContainer,
  'kbd': TransparentContainer,
  'code': TransparentContainer,
  'cite': TransparentContainer,
  'small': TransparentContainer,
  'big': TransparentContainer,
  'font': TransparentContainer,
  'noscript': TransparentContainer,
  'form': TransparentContainer,
  'span': TransparentContainer,
  'br': LineBreakComponent,
  'hr': HorizontalBreakComponent,
  'table': TableContainer,
  'pre': BlockContainer,
  'blockquote': BlockContainer,
  #'map': XXX,
  #'input': XXX,
  #'select': XXX,
  'nobr': TransparentContainer,
  })


#####################################################################

# main
def main(argv):
  import getopt, urllib
  from htmldom import parse_html
  from style import ActiveStyleSheet
  from device import DebugDevice
  def usage():
    print 'usage: layout.py [-d] [-b base_href] [-c charset_in] [-C codec_out] [-w width] [url ...]'
    return 100
  try:
    (opts, args) = getopt.getopt(argv[1:], 'dc:C:w:b:')
  except getopt.GetoptError:
    return usage()
  (charset_in, codec_out, width, base_href) = (None, 'ascii', 80, '')
  for (k, v) in opts:
    if k == '-d':
      Placer.debug += 1
      Component.debug += 1
    elif k == '-c': charset_in = v
    elif k == '-C': codec_out = v
    elif k == '-w': width = int(v)
    elif k == '-b': base_href = v
  for url in args:
    device = DebugDevice(width, 24)
    fp = urllib.urlopen(url)
    stylesheet = ActiveStyleSheet(device=device)
    stylesheet.set_base(base_href or url)
    stylesheet.parse_lines([
      u'ul { text-indent: 2em; }\n',
      u'hr { width: 100%; margin-top: 1em; margin-bottom: 1em; }\n',
      u'table { table-border: 1em; }\n',
      u'p,center,h1,h2,h3,h4,h5,h6,hr,ul { para-space: 1em; }\n',
      ])
    root = parse_html(fp, base_href=url, charset=charset_in, stylesheet=stylesheet)
    fp.close()
    top = Frame(root, device.width, stylesheet)
    top.build()
    top.render(0, 0)
  return

if __name__ == '__main__': sys.exit(main(sys.argv))
